#!/usr/bin/env python
#coding=utf-8

import os
import sys
import uvicorn
from fastapi import FastAPI, HTTPException, Request
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse, PlainTextResponse, Response
from fastapi.middleware.cors import CORSMiddleware
import mimetypes
import urllib.parse
import json

# Add the project root to Python path
sys.path.append(os.path.dirname(os.path.realpath(__file__)))

import config
from rest.products import Products

app = FastAPI(title="OHOS ArchInfo API", version="1.0.0")

# Add CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3001", "http://127.0.0.1:3001", "http://localhost:8081", "http://127.0.0.1:8081"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Initialize the products manager
products_mgr = Products()

# Mount static files
if os.path.exists(config.HTML_PATH):
    app.mount("/static", StaticFiles(directory=config.HTML_PATH), name="static")

@app.get("/", response_class=HTMLResponse)
async def read_root():
    """Serve the main index page"""
    index_path = os.path.join(config.HTML_PATH, "index.html")
    if os.path.exists(index_path):
        with open(index_path, 'r', encoding='utf-8') as f:
            return HTMLResponse(content=f.read())
    return HTMLResponse(content="<h1>OHOS ArchInfo Service</h1><p>Service is running</p>")

@app.get("/symdb/products")
async def get_products():
    """Get all available products"""
    try:
        from rest.utils.rester import ResterHelper
        xargs = {}
        content = ResterHelper.build_array_content(products_mgr.get_all(), xargs)
        return PlainTextResponse(content=content, media_type="text/plain")
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


# Special endpoints for modules stats and analysis
@app.get("/symdb/products/{product_name}/{product_version}/modules/simple-test")
async def get_modules_simple_test(product_name: str, product_version: str, request: Request):
    """Simple test API for debugging"""
    return {"message": "Simple test working", "product": product_name, "version": product_version}

@app.get("/symdb/products/{product_name}/{product_version}/modules/stats")
async def get_modules_stats(product_name: str, product_version: str, request: Request):
    """Get modules statistics"""
    print(f"DEBUG: get_modules_stats called with product={product_name}, version={product_version}")
    try:
        # Get the product
        product = products_mgr.get_product_by_name(product_name, product_version)
        print(f"DEBUG: product found: {product}")
        if not product:
            raise HTTPException(status_code=404, detail="Product not found")

        # Ensure product is loaded
        if not product._loaded:
            product.load_db()

        # Get modules manager
        modules_mgr = product.getMgr("modules")
        if not modules_mgr:
            raise HTTPException(status_code=500, detail="Modules manager not found")

        # Get modules data using existing format
        try:
            all_modules = modules_mgr.get_all()
            if hasattr(all_modules, '__iter__') and not isinstance(all_modules, (str, bytes)):
                all_modules = list(all_modules)

            total_modules = len(all_modules)
            components = set()

            # Simple stats calculation
            for module in all_modules:
                # Extract component name - handle different data formats
                if hasattr(module, 'get') and callable(module.get):
                    component_name = module.get('componentName')
                else:
                    component_name = getattr(module, 'componentName', None)

                if component_name:
                    components.add(str(component_name))

            # Create simple stats
            stats = {
                "totalModules": total_modules,
                "totalComponents": len(components),
                "totalSymbols": 0,
                "totalDependencies": 0,
                "avgDependencyDepth": 0
            }

            return PlainTextResponse(content=json.dumps(stats), media_type="text/plain")
        except Exception as e:
            raise HTTPException(status_code=500, detail=f"Stats calculation error: {str(e)}")

    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/symdb/products/{product_name}/{product_version}/modules/top")
async def get_modules_top(product_name: str, product_version: str, request: Request):
    """Get top modules"""
    try:
        # Parse query parameters
        query_params = dict(request.query_params)
        limit = int(query_params.get("limit", 10))

        # Get the product
        product = products_mgr.get_product_by_name(product_name, product_version)
        if not product:
            raise HTTPException(status_code=404, detail="Product not found")

        # Ensure product is loaded
        if not product._loaded:
            product.load_db()

        # Get modules manager
        modules_mgr = product.getMgr("modules")
        if not modules_mgr:
            raise HTTPException(status_code=500, detail="Modules manager not found")

        # Get top modules
        try:
            all_modules = modules_mgr.get_all()
            if hasattr(all_modules, '__iter__') and not isinstance(all_modules, (list, tuple, str)):
                all_modules = list(all_modules)

            # Simple top modules - just return first N modules
            top_modules = all_modules[:limit]

            # Format response - handle array format
            result = []
            for module in top_modules:
                try:
                    # Handle array format: [id, type, subsystem, component, name, modGroup, deps_total, deps_indirect, deps_self, dependedBy, ...]
                    if isinstance(module, (list, tuple)) and len(module) > 10:
                        result.append({
                            "id": module[0],
                            "name": module[4] if len(module) > 4 else "",
                            "component": module[3] if len(module) > 3 else "",
                            "deps_count": int(module[6]) if len(module) > 6 and module[6] else 0,
                            "depended_by_count": int(module[9]) if len(module) > 9 and module[9] else 0,
                            "size": int(module[24]) if len(module) > 24 and module[24] else 0
                        })
                    else:
                        # Fallback for object format
                        module_data = module
                        if hasattr(module, 'get') and callable(module.get):
                            module_data = module
                        elif hasattr(module, '__dict__'):
                            class DictWrapper:
                                def __init__(self, obj):
                                    self.obj = obj
                                def get(self, key, default=None):
                                    return getattr(self.obj, key, default)
                            module_data = DictWrapper(module)

                        if module_data:
                            result.append({
                                "id": module_data.get("id"),
                                "name": module_data.get("name", ""),
                                "component": module_data.get("componentName", ""),
                                "deps_count": int(module_data.get("deps_total") or module_data.get("deps", 0) or 0),
                                "depended_by_count": int(module_data.get("dependedBy_total") or module_data.get("dependedBy", 0) or 0),
                                "size": int(module_data.get("size", 0)) if module_data.get("size") else 0
                            })
                except Exception as e:
                    # Skip problematic modules but continue processing
                    print(f"Warning: Error processing module data: {e}")
                    continue

            return PlainTextResponse(content=json.dumps(result), media_type="text/plain")
        except Exception as e:
            raise HTTPException(status_code=500, detail=f"Top modules error: {str(e)}")

    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/symdb/products/{product_name}/{product_version}/modules/distribution/types")
async def get_modules_distribution_types(product_name: str, product_version: str, request: Request):
    """Get module distribution types"""
    try:
        # Get the product
        product = products_mgr.get_product_by_name(product_name, product_version)
        if not product:
            raise HTTPException(status_code=404, detail="Product not found")

        # Ensure product is loaded
        if not product._loaded:
            product.load_db()

        # Get modules manager
        modules_mgr = product.getMgr("modules")
        if not modules_mgr:
            raise HTTPException(status_code=500, detail="Modules manager not found")

        # Count module types
        try:
            all_modules = modules_mgr.get_all()
            type_counts = {}

            for module in all_modules:
                if hasattr(module, 'get') and callable(module.get):
                    # Simple module type classification
                    module_type = "其他"
                    module_name = module.get("name", "")
                    if module_name:
                        if module_name.endswith(".so"):
                            module_type = "动态库"
                        elif module_name.endswith(".a"):
                            module_type = "静态库"
                        elif module_name.endswith(".bin"):
                            module_type = "可执行文件"

                    type_counts[module_type] = type_counts.get(module_type, 0) + 1

            # Format for chart
            result = [{"name": k, "value": v} for k, v in type_counts.items()]

            return PlainTextResponse(content=json.dumps(result), media_type="text/plain")
        except Exception as e:
            raise HTTPException(status_code=500, detail=f"Distribution types error: {str(e)}")

    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.get("/symdb/products/{product_name}/{product_version}/{resource}")
async def get_product_resource(product_name: str, product_version: str, resource: str, request: Request):
    """Handle product-specific resource requests"""
    try:
        # Parse query parameters
        query_params = dict(request.query_params)

        # Get the product
        product = products_mgr.get_product_by_name(product_name, product_version)
        if not product:
            raise HTTPException(status_code=404, detail="Product not found")

        # Create a mock handler for compatibility
        class MockHandler:
            def __init__(self):
                self.response_content = None
                self.headers = {}

            def resFileNotFound(self):
                # Mock implementation - this will raise HTTPException in our case
                raise HTTPException(status_code=404, detail="Resource not found")

            def resStrContent(self, content, xargs=None):
                self.response_content = content
                # Determine content type based on format
                if xargs and "format" in xargs:
                    if xargs["format"] == "svg":
                        self.headers["content-type"] = "image/svg+xml"
                    elif xargs["format"] == "xml":
                        self.headers["content-type"] = "text/xml"
                        self.headers["content-disposition"] = f'attachment; filename="{product_name}_{product_version}_{resource}.xml"'

        mock_handler = MockHandler()
        path_parts = [config.REST_PREFIX, product_name, product_version, resource]

        # Process the request using existing logic
        product.doGetRequest(mock_handler, path_parts, query_params)

        if mock_handler.response_content:
            return Response(
                content=mock_handler.response_content,
                media_type=mock_handler.headers.get("content-type", "text/plain"),
                headers={
                    k: v for k, v in mock_handler.headers.items()
                    if k != "content-type"
                }
            )
        else:
            raise HTTPException(status_code=404, detail="Resource not found")

    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/symdb/products/{product_name}/{product_version}/{resource}/{resource_id}")
async def get_product_resource_item(product_name: str, product_version: str, resource: str, resource_id: str, request: Request):
    """Handle specific product resource item requests"""
    try:
        # Parse query parameters
        query_params = dict(request.query_params)

        # Get the product
        product = products_mgr.get_product_by_name(product_name, product_version)
        if not product:
            raise HTTPException(status_code=404, detail="Product not found")

        # Create a mock handler for compatibility
        class MockHandler:
            def __init__(self):
                self.response_content = None
                self.headers = {}

            def resFileNotFound(self):
                # Mock implementation - this will raise HTTPException in our case
                raise HTTPException(status_code=404, detail="Resource not found")

            def resStrContent(self, content, xargs=None):
                self.response_content = content
                # Determine content type based on format
                if xargs and "format" in xargs:
                    if xargs["format"] == "svg":
                        self.headers["content-type"] = "image/svg+xml"
                    elif xargs["format"] == "xml":
                        self.headers["content-type"] = "text/xml"
                        self.headers["content-disposition"] = f'attachment; filename="{product_name}_{product_version}_{resource}_{resource_id}.xml"'

        mock_handler = MockHandler()
        path_parts = [config.REST_PREFIX, product_name, product_version, resource, resource_id]

        # Process the request using existing logic
        product.doGetRequest(mock_handler, path_parts, query_params)

        if mock_handler.response_content:
            return Response(
                content=mock_handler.response_content,
                media_type=mock_handler.headers.get("content-type", "text/plain"),
                headers={
                    k: v for k, v in mock_handler.headers.items()
                    if k != "content-type"
                }
            )
        else:
            raise HTTPException(status_code=404, detail="Resource item not found")

    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.get("/symdb/products/{product_name}/{product_version}/{resource}/{resource_id}/{operation}")
async def get_product_resource_operation(product_name: str, product_version: str, resource: str, resource_id: str, operation: str, request: Request):
    """Handle resource operations like modules/{id}/fields, modules/{id}/details"""
    try:
        # Parse query parameters
        query_params = dict(request.query_params)

        # Get the product
        product = products_mgr.get_product_by_name(product_name, product_version)
        if not product:
            raise HTTPException(status_code=404, detail="Product not found")

        # Create a mock handler for compatibility
        class MockHandler:
            def __init__(self):
                self.response_content = None
                self.headers = {}

            def resFileNotFound(self):
                # Mock implementation - this will raise HTTPException in our case
                raise HTTPException(status_code=404, detail="Resource not found")

            def resStrContent(self, content, xargs=None):
                self.response_content = content
                # Determine content type based on format
                if xargs and "format" in xargs:
                    if xargs["format"] == "svg":
                        self.headers["content-type"] = "image/svg+xml"
                    elif xargs["format"] == "xml":
                        self.headers["content-type"] = "text/xml"
                        self.headers["content-disposition"] = f'attachment; filename="{product_name}_{product_version}_{resource}_{resource_id}_{operation}.xml"'

        mock_handler = MockHandler()

        # Include the operation in the path_parts for the REST handler
        path_parts = [config.REST_PREFIX, product_name, product_version, resource, resource_id, operation]

        # Process the request using existing logic
        product.doGetRequest(mock_handler, path_parts, query_params)

        if mock_handler.response_content:
            return Response(
                content=mock_handler.response_content,
                media_type=mock_handler.headers.get("content-type", "text/plain"),
                headers={
                    k: v for k, v in mock_handler.headers.items()
                    if k != "content-type"
                }
            )
        else:
            raise HTTPException(status_code=404, detail="Resource operation not found")

    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/symdb/products/{product_name}/{product_version}/{resource}/{resource_id}/{operation}/{sub_operation}")
async def get_product_resource_sub_operation(product_name: str, product_version: str, resource: str, resource_id: str, operation: str, sub_operation: str, request: Request):
    """Handle sub-operations like modules/{id}/fields/{type}"""
    try:
        # Parse query parameters
        query_params = dict(request.query_params)

        # Get the product
        product = products_mgr.get_product_by_name(product_name, product_version)
        if not product:
            raise HTTPException(status_code=404, detail="Product not found")

        # Create a mock handler for compatibility
        class MockHandler:
            def __init__(self):
                self.response_content = None
                self.headers = {}

            def resFileNotFound(self):
                # Mock implementation - this will raise HTTPException in our case
                raise HTTPException(status_code=404, detail="Resource not found")

            def resStrContent(self, content, xargs=None):
                self.response_content = content
                # Determine content type based on format
                if xargs and "format" in xargs:
                    if xargs["format"] == "svg":
                        self.headers["content-type"] = "image/svg+xml"
                    elif xargs["format"] == "xml":
                        self.headers["content-type"] = "text/xml"
                        self.headers["content-disposition"] = f'attachment; filename="{product_name}_{product_version}_{resource}_{resource_id}_{operation}_{sub_operation}.xml"'

        mock_handler = MockHandler()

        # Include the operation and sub_operation in the path_parts for the REST handler
        path_parts = [config.REST_PREFIX, product_name, product_version, resource, resource_id, operation, sub_operation]

        # Process the request using existing logic
        product.doGetRequest(mock_handler, path_parts, query_params)

        if mock_handler.response_content:
            return Response(
                content=mock_handler.response_content,
                media_type=mock_handler.headers.get("content-type", "text/plain"),
                headers={
                    k: v for k, v in mock_handler.headers.items()
                    if k != "content-type"
                }
            )
        else:
            raise HTTPException(status_code=404, detail="Resource sub-operation not found")

    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

# Catch-all handler for static files and fallback
@app.get("/{path:path}")
@app.head("/{path:path}")
async def catch_all(path: str, request: Request):
    """Serve static files or return 404"""
    # Try to serve as static file
    file_path = os.path.join(config.HTML_PATH, path)
    if os.path.exists(file_path) and os.path.isfile(file_path):
        # Determine media type
        _, ext = os.path.splitext(file_path)
        if ext == '.js':
            media_type = "application/javascript"
        elif ext == '.css':
            media_type = "text/css"
        else:
            media_type = mimetypes.types_map.get(ext, "text/plain")

        # Get file size for Content-Length header
        file_size = os.path.getsize(file_path)

        # Handle HEAD request - return headers only
        if request.method == "HEAD":
            return Response(
                content=None,
                media_type=media_type,
                headers={"Content-Length": str(file_size)}
            )

        # Handle GET request - return full content
        with open(file_path, 'rb') as f:
            content = f.read()

        return Response(content=content, media_type=media_type)

    # Check if this is a request for static assets (js, css, images, etc.)
    # If so, return 404 instead of serving index.html
    _, ext = os.path.splitext(path.lower())
    static_extensions = {'.js', '.css', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.woff', '.woff2', '.ttf', '.eot'}

    if ext in static_extensions:
        # For static assets that don't exist, return 404 immediately
        raise HTTPException(status_code=404, detail="Static file not found")

    # For other requests (like HTML pages), try to serve index.html for SPA routing
    index_path = os.path.join(config.HTML_PATH, "index.html")
    if os.path.exists(index_path):
        # Handle HEAD request for index.html
        if request.method == "HEAD":
            file_size = os.path.getsize(index_path)
            return Response(
                content=None,
                media_type="text/html",
                headers={"Content-Length": str(file_size)}
            )

        # Handle GET request for index.html
        with open(index_path, 'r', encoding='utf-8') as f:
            return HTMLResponse(content=f.read())

    raise HTTPException(status_code=404, detail="File not found")

if __name__ == "__main__":
    print(f"Starting OHOS ArchInfo Service on {config.SERVER_IP}:{config.SERVER_PORT}")
    uvicorn.run(
        "main:app",
        host=config.SERVER_IP,
        port=config.SERVER_PORT,
        reload=False,
        log_level="info"
    )